﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Input;
using System.Globalization;
using System.Reflection;
using System.Collections.Generic;

namespace MichMan.Utilities
{
    /// <summary>
    /// Provides compatibility functions for Metro apps.
    /// </summary>
    public static class TypeExtensions
    {
        public static IEnumerable<PropertyInfo> GetProperties(this Type type)
        {
            Type curType = type;
            while (curType != null)
            {
                foreach (var item in curType.GetTypeInfo().DeclaredProperties)
                {
                    yield return item;
                }
                curType = curType.GetTypeInfo().BaseType;
            }
        }

        public static IEnumerable<Type> GetInterfaces(this Type type)
        {
            Type curType = type;
            while (curType != null)
            {
                foreach (var item in curType.GetTypeInfo().ImplementedInterfaces)
                {
                    yield return item;
                }
                curType = curType.GetTypeInfo().BaseType;
            }
        }

        public static IEnumerable<MethodInfo> GetMethods(this Type type)
        {
            Type curType = type;
            while (curType != null)
            {
                foreach (var item in curType.GetTypeInfo().DeclaredMethods)
                {
                    yield return item;
                }
                curType = curType.GetTypeInfo().BaseType;
            }
        }

        public static IEnumerable<FieldInfo> GetFields(this Type type)
        {
            Type curType = type;
            while (curType != null)
            {
                foreach (var item in curType.GetTypeInfo().DeclaredFields)
                {
                    yield return item;
                }
                curType = curType.GetTypeInfo().BaseType;
            }
        }

        public static IEnumerable<Attribute> GetCustomAttributes(this Type type)
        {
            return type.GetTypeInfo().GetCustomAttributes();
        }

        public static IEnumerable<T> GetCustomAttributes<T>(this Type type) where T : Attribute
        {
            return type.GetTypeInfo().GetCustomAttributes<T>();
        }

        public static IEnumerable<Attribute> GetCustomAttributes(this Type type, bool inherit)
        {
            return type.GetTypeInfo().GetCustomAttributes(inherit);
        }

        public static IEnumerable<T> GetCustomAttributes<T>(this Type type, bool inherit) where T : Attribute
        {
            return type.GetTypeInfo().GetCustomAttributes<T>(inherit);
        }
    }

    public static class ObjectExtensions
    {
        public static bool IsOverridden(this object obj, string methodName)
        {
            Type objType = obj.GetType();

            MethodInfo mi = objType.GetTypeInfo().GetDeclaredMethod(methodName);

            return (mi != null);
        }
    }

    /// <summary>
    /// Simple implementation of "Instance" singleton property.
    /// 
    /// It's up to the class T to ensure that it's constructor is non-public so outsiders can't set the instance.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public static class Singleton<T> where T : class
    {
        private static T _instance;

        /// <summary>
        /// Call this fom your classes Instance implementation. 
        /// </summary>
        /// <param name="Constructor"></param>
        /// <returns></returns>
        public static T Create(Func<T> Constructor)
        {
            if (_instance == null)
            {
                _instance = Constructor();
            }
            return _instance;
        }

        /// <summary>
        /// If you need to test if Create was ever called.  Useful for deciding if you should save object state.
        /// 
        /// Note that anyone can call Get, but only those who can create an object of type T can call Create or Set.
        /// </summary>
        /// <returns></returns>
        public static T Get()
        {
            return _instance;
        }

        /// <summary>
        /// Sets instance to a particular value.  Useful for a subclass to override the default constructor.
        /// </summary>
        /// <param name="instance"></param>
        public static void Set(T instance)
        {
            _instance = instance;
        }
    }

    /// <summary>
    /// Delay-get an object.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Getter<T> where T : class
    {
        public Getter(Func<T> predicate)
        {
            this.predicate = predicate;
        }

        Func<T> predicate;

        public bool Has { get { return _item != null; } }

        private T _item = null;
        public T Value()
        {
            if (_item == null)
            {
                _item = predicate();
            }
            return _item;
        }
    }

    /// <summary>
    /// Property True returns true the first time the condition is true.  Otherwise, returns false.
    /// 
    /// Useful for checking to see if an object was created since the last call.
    /// 
    /// Note:  This should have a constructor with a threadsafe option.  
    /// But I don't need that right now.
    /// </summary>
    public class Once
    {
        /// <summary>
        /// With this constructor, it will be true the first time it is checked.
        /// </summary>
        public Once()
            : this(() => true)
        {
        }

        public Once(Func<bool> predicate)
        {
            Predicate = predicate;
        }

        /// <summary>
        /// Declare tha the event happened.
        /// 
        /// There is no reset.  Just create a new Once.
        /// </summary>
        public void Set()
        {
            _happenedYet = true;
        }

        public Func<bool> Predicate { get; private set; }
        private bool _happenedYet = false;

        /// <summary>
        /// Returns true if the predicate is now true where before it had been false.
        /// </summary>
        public bool Value
        {
            get
            {
                if (_happenedYet || Predicate == null)
                {
                    return false;
                }

                if (Predicate())
                {
                    _happenedYet = true;
                }
                return true;
            }
        }
    }

    /// <summary>
    /// Property True returns true if the value has changed either since the object was last checked.
    /// 
    /// Will always be true the first time you call True.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Changed<T>
    {
        public Changed(Func<T> predicate)
        {
            Predicate = predicate;
            lastCheckedValue = null;
        }

        public Func<T> Predicate { get; private set; }
        private bool everChecked = false;
        private object lastCheckedValue;

        public bool Value
        {
            get
            {
                if (Predicate == null)
                {
                    return false;
                }

                // This allows me to box literals and treat them as objects.
                object value = Predicate();

                if (value != null)
                {
                    if (!value.Equals(lastCheckedValue))
                    {
                        lastCheckedValue = value;
                        everChecked = true;
                        return true;
                    }
                }

                if (value == null && lastCheckedValue != null)
                {
                    lastCheckedValue = value;
                    everChecked = true;
                    return true;
                }

                if (!everChecked)
                {
                    everChecked = true;
                    return true;
                }

                return false;
            }
        }
    }

    /// <summary>
    /// Provides a simple implementation of IFormatProvider and ICustomFormatter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CustomFormatProvider<T> : ICustomFormatter, IFormatProvider
    {
        public CustomFormatProvider(Func<T, string> formatter)
        {
            Formatter = (f,a) => formatter(a);
        }

        public CustomFormatProvider(Func<string, T, string> formatter)
        {
            Formatter = formatter;
        }

        public Func<string, T, string> Formatter { get; private set; }

        // IFormatProvider.GetFormat implementation.
        public object GetFormat(Type formatType)
        {
            // Determine whether custom formatting object is requested.
            if (formatType == typeof(ICustomFormatter))
                return this;
            else
                return null;
        }

        // ICustomFormatter.Format implementation.
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg is T)
            {
                return Formatter(format, (T)arg);
            }

            return HandleOtherFormats(format, arg);
        }

        private string HandleOtherFormats(string format, object arg)
        {
            if (arg is IFormattable)
                return ((IFormattable)arg).ToString(format, CultureInfo.CurrentCulture);
            else if (arg != null)
                return arg.ToString();
            else
                return String.Empty;
        }
    }

    /// <summary>
    /// Base class for an attribute that provides formatting services.
    /// </summary>
    public class FormattableAttribute : Attribute
    {
        public virtual string ToString(object value)
        {
            return Format("{0}", value);
        }

        public string Format(string format, object value)
        {
            if (FormatProvider != null)
            {
                return String.Format(FormatProvider, format, value);
            }
            return value.ToString();
        }

        /// <summary>
        /// Format provider to use when formatting this attribute.  If none is specified, ToString() will be used.
        /// </summary>
        public Type FormatProviderType { get; set; }

        public IFormatProvider FormatProvider
        {
            get
            {
                if (FormatProviderType != null)
                {
                    if (typeof(IFormatProvider).GetTypeInfo().IsAssignableFrom(FormatProviderType.GetTypeInfo()))
                    {
                        return Activator.CreateInstance(FormatProviderType) as IFormatProvider;
                    }
                }
                return null;
            }
        }
    }
}
